/**
 * canvas画板
 * 兼容移动端touch事件
 * ----------传入的参数-----------
 * @tag                  canvas的id
 * @historyList          历史纪录的点
 * @background           背景色
 * @color                画笔颜色
 * @size                 画笔大小
 * ----------提供的方法-----------
 * @resetCanvas          清空canvas的方法
 * @init                 还原所有画笔的画布 options {[{size: Number,color: String,list: [[12,13],[14,15]], {img, x, y, w, h, isCover}}]}
 * @after                后一步画布-----code=404没有前一笔, code=200成功
 * @front                前一步画布-----code=404没有前一笔, code=200成功
 * @getPointList         获取画笔记录
 * @setColor             设置颜色 options String
 * @setPenSize           设置画笔的大小 options Number
 * @eraser               橡皮檫
 * @getbase64            获取canvas的base64 options (质量0-1越大越好, 图片类型)
 * @drawImg              设置图片 options (图片源, 位置x, 位置y, 图片宽, 图片高, 是否被历史画笔覆盖)
 * @downland             下载canvas图片文件 options 文件名字, 质量0-1越大越好, 图片类型
 * @getBase64ImgFile     获取canvas图片file类型(区别于base64字符串) options (质量0-1越大越好, 图片类型, 文件名)
 * @clearPixel           设置清除像素画笔
 * @setNoDraw            特殊场景比如移动元素的情况下不进行绘画
 */

export interface historyType {
  size?: number;
  color?: CSSStyleDeclaration["color"];
  list?: [number, number][];
  isClearPixel?: boolean;
  img?: CanvasImageSource;
  isCover?: boolean;
  x?: number;
  y?: number;
  w?: number;
  h?: number;
}

export default class Draw {
  public canvas: HTMLCanvasElement;
  public ctx: CanvasRenderingContext2D;
  public width: number;
  public height: number;
  public canvasOffsetTop: number;
  public canvasOffsetLeft: number;
  public startPosition: null | [number, number];
  public isdraw: boolean;
  public history: historyType[];
  public isInit: boolean;
  public currentPen: [number, number][];
  public drawStep: number;
  public penSize: number;
  public penColor: CSSStyleDeclaration["color"];
  public backGround: CSSStyleDeclaration["color"];
  public beforeEarserColor: CSSStyleDeclaration["color"] | null;
  public isEraser: boolean;
  public isClearPixel: boolean;
  public noDraw: boolean;
  public drawCallback?: (e: Draw) => void;

  constructor(
    tag: string,
    color: CSSStyleDeclaration["color"],
    size: number,
    historyList?: [],
    background?: CSSStyleDeclaration["color"],
    drawCallback?: (e: Draw) => void
  ) {
    // @ts-ignore
    this.canvas = document.getElementById(tag)
      ? document.getElementById(tag)
      : document.createElement("canvas");
    // @ts-ignore
    this.ctx = this.canvas.getContext("2d");
    this.width = this.canvas.offsetWidth;
    this.height = this.canvas.offsetHeight;
    this.canvas.width = this.width;
    this.canvas.height = this.height;
    this.canvasOffsetTop = 0; //移动端canvas距离顶部的距离（在每次触摸的时候获取）
    this.canvasOffsetLeft = 0; //移动端canvas距离左边的距离
    this.startPosition = null; //当前的点
    this.isdraw = false; //是否在作画
    this.history = historyList ? historyList : []; //所有的历史画笔集合
    this.isInit = false; //是否为初始化
    this.currentPen = []; //当前画笔点的集合
    this.drawStep = 0; //当前第几笔画
    this.penSize = size; //画笔大小
    this.penColor = color; //画笔颜色
    this.backGround = background ? background : "#999999"; //画布背景色
    this.ctx.fillStyle = this.backGround;
    this.ctx.lineWidth = size * 2 + 1; //线的宽度
    this.ctx.strokeStyle = color; //点的颜色
    this.beforeEarserColor = null; //橡皮檫之前的颜色
    this.isEraser = false; //是否为橡皮檫状态
    this.isClearPixel = false; //是否是清除像素的模式
    this.noDraw = false; // 特殊场景比如移动元素的情况下不进行绘画
    this.drawCallback = (e) => drawCallback?.(e);

    this.canvas.onmousedown = (res) => {
      //指针在canvas上点击
      this.isdraw = true;
      this.startPosition = [res.offsetX, res.offsetY];
      this.drawLine(this.startPosition[0], this.startPosition[1]);
    };
    this.canvas.onmouseup = (res) => {
      //指针在canvas上放开
      if (!this.isdraw) return;
      this.clear();
    };
    this.canvas.onmouseout = (res) => {
      //指针移出canvas
      if (!this.isdraw) return;
      this.clear();
    };
    this.canvas.onmousemove = (res) => {
      //指针在canvas移动
      if (!this.isdraw) return;
      this.drawLine(res.offsetX, res.offsetY);
    };

    this.ctx.fillRect(0, 0, this.width, this.height); //画布大小
    this.canvas.addEventListener(
      "touchmove",
      (e) => {
        //移动端禁止页面滚动
        e.preventDefault();
      },
      {
        passive: false,
      }
    ); //passive 参数不能省略，用来兼容ios和android
    // @ts-ignore
    this.canvas.removeEventListener(
      "touchend",
      (e) => {
        //移动端解除页面滚动
        e.preventDefault();
      },
      {
        passive: false,
      }
    );
    this.canvas.ontouchstart = (res) => {
      // 手指触摸开始
      this.isdraw = true;
      this.canvasOffsetTop = this.canvas.offsetTop; //避免canvas在其他dom元素下面其他dom元素没有渲染初始化的距离错误
      this.canvasOffsetLeft = this.canvas.offsetLeft;
      this.startPosition = [
        res.changedTouches[0].pageX - this.canvasOffsetLeft,
        res.changedTouches[0].pageY - this.canvasOffsetTop,
      ];
      this.drawLine(this.startPosition[0], this.startPosition[1]);
    };
    this.canvas.ontouchmove = (res) => {
      //手指触摸移动
      if (!this.isdraw) return;
      this.drawLine(
        res.changedTouches[0].pageX - this.canvasOffsetLeft,
        res.changedTouches[0].pageY - this.canvasOffsetTop
      );
    };
    this.canvas.ontouchend = (res) => {
      // 手指触摸结束
      if (!this.isdraw) return;
      this.clear();
    };
  }

  drawClearPixel(cx: number, cy: number) {
    // 单点清除像素点
    for (let i = 0; i <= this.penSize; i++) {
      let itemRact = Math.sqrt(Math.pow(this.penSize, 2) - Math.pow(i, 2));
      if (i === 0) {
        this.ctx.clearRect(cx - this.penSize, cy, this.penSize * 2, 1); // x,y,w,h
      } else if (i === this.penSize) {
        this.ctx.clearRect(cx, cy - this.penSize, 1, this.penSize * 2);
      } else {
        this.ctx.clearRect(cx - i, cy - itemRact, 1, itemRact * 2);
        this.ctx.clearRect(cx + i, cy - itemRact, 1, itemRact * 2);
      }
    }
  }

  drawLine(X: number, Y: number) {
    //画图
    if (!this.isdraw || this.noDraw) return;
    if (this.isClearPixel) {
      // 清除像素鼠标移动过快出现间隔计算
      if (this.startPosition) {
        let distance = Math.sqrt(
          Math.pow(Math.abs(this.startPosition[0] - X), 2) +
            Math.pow(Math.abs(this.startPosition[1] - Y), 2)
        ); // 鼠标移动当前点距离上一个点的距离
        let addPointerLength = Math.floor(distance / (this.penSize / 2)); // 细腻度
        let itemX = (X - this.startPosition[0]) / addPointerLength;
        let itemY = (Y - this.startPosition[1]) / addPointerLength;
        if (itemX && itemY) {
          // 指针过快移动的过程中加入点磨平点和点间隔
          for (let i = 1; i <= addPointerLength; i++) {
            let currentAddPointer: [number, number] = [
              this.startPosition[0] + itemX,
              this.startPosition[1] + itemY,
            ];
            this.drawClearPixel(currentAddPointer[0], currentAddPointer[1]);
            this.startPosition = currentAddPointer;
            if (!this.isInit) this.currentPen.push(currentAddPointer);
          }
        }
      } else {
        this.drawClearPixel(X, Y); // 第一笔
        this.startPosition = [X, Y]; // 作用于历史画笔
      }
    } else {
      if (this.startPosition) {
        this.ctx.beginPath();
        this.ctx.arc(X, Y, this.penSize, 0, 2 * Math.PI);
        this.ctx.closePath();
        this.ctx.fillStyle = this.penColor;
        this.ctx.fill();
        this.ctx.beginPath();
        this.ctx.moveTo(this.startPosition[0], this.startPosition[1] + 0.5);
        this.ctx.lineTo(X, Y + 0.5);
        this.ctx.stroke();
        this.ctx.save();
        this.startPosition = [X, Y];
        if (!this.isInit) this.currentPen.push(this.startPosition);
      }
    }
  }
  clear() {
    // 停止画笔
    this.isdraw = false;
    this.startPosition = null;
    this.history.push({
      size: this.penSize,
      color: this.penColor,
      list: this.currentPen,
      isClearPixel: this.isClearPixel,
    });
    this.currentPen = [];
    this.drawStep = this.history.length - 1;
    this.drawCallback?.(this);
  }
  async historyDrawPen(penList: historyType[] = []) {
    // 画历史画笔
    this.history = penList;
    this.isdraw = true;
    this.isInit = true;
    this.ctx.clearRect(0, 0, this.width, this.height);
    this.resetCanvas();
    for (let i = 0; i < penList.length; i++) {
      if (this.drawStep < i) break;
      let { img, x, y, w, h, list, color, size, isClearPixel } = penList[i];
      if (
        img &&
        x?.constructor &&
        y?.constructor &&
        w?.constructor &&
        h?.constructor
      ) {
        this.ctx.drawImage(img, x, y, w, h);
      } else {
        if (list?.length === 0) {
          this.resetCanvas();
        } else {
          let currentIsClearPixel = this.isClearPixel;
          color && this.setColor(color);
          size && this.setPenSize(size);
          this.clearPixel(!!isClearPixel);
          this.startPosition = null; //避免画笔的最后一点会和下一个画笔的前一点链接起来
          if (list) {
            for (let j = 0; j < list.length; j++) {
              this.drawLine(list[j][0], list[j][1]);
            }
          }
          this.clearPixel(currentIsClearPixel);
        }
      }
    }
    this.isdraw = false;
    this.isInit = false;
    this.startPosition = null;
  }

  getHistoryPoint() {
    // 返回画笔记录
    return this.history;
  }
  setColor(color: CSSStyleDeclaration["color"]) {
    // 设置画笔的颜色
    if (this.isEraser) {
      this.beforeEarserColor = color;
      return;
    }
    this.ctx.strokeStyle = color;
    this.penColor = color;
  }
  setPenSize(size: number) {
    // 设置画笔的粗细
    this.penSize = size;
    this.ctx.lineWidth = size * 2 + 1;
  }
  clearPixel(data: boolean) {
    this.isClearPixel = data;
  }
  resetCanvas() {
    //清空canvas
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.currentPen = [];
    this.ctx.fillStyle = this.backGround;
    this.ctx.fillRect(0, 0, this.width, this.height);
    if (this.isInit) return;
    this.history.push({
      size: this.penSize,
      color: this.penColor,
      list: [],
    });
    this.drawStep = this.history.length - 1;
  }
  init(penList: historyType[]) {
    // 初始化画布
    this.isInit = true;
    this.drawStep = penList ? penList.length - 1 : 0;
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this.ctx.fillStyle = this.backGround;
    this.ctx.fillRect(0, 0, this.width, this.height);
    this.historyDrawPen(penList);
  }
  after() {
    // 下一笔
    if (this.drawStep >= this.history.length - 1)
      return {
        code: 404,
        msg: "没有下一个笔画了",
      };
    this.drawStep = this.drawStep + 1;
    this.historyDrawPen(this.history);
    return {
      code: 200,
      msg: "恢复下一笔",
    };
  }
  front() {
    // 上一笔
    if (this.drawStep < 0)
      return {
        code: 404,
        msg: "没有前一个笔画了",
      };
    this.drawStep = this.drawStep - 1;
    this.historyDrawPen(this.history);
    return {
      code: 200,
      msg: "恢复前一笔",
    };
  }
  eraser(data: boolean) {
    // 橡皮檫
    if (data) {
      this.beforeEarserColor = this.penColor;
      this.setColor(this.backGround);
      this.isEraser = true;
    } else {
      this.isEraser = false;
      this.beforeEarserColor && this.setColor(this.beforeEarserColor);
      this.beforeEarserColor = null;
    }
  }
  setNoDraw(data: boolean) {
    this.noDraw = data;
  }
  getbase64(quality = 1, type = "image/png") {
    // 获取canvas的base64, jpeg导出是白底，png才有透明色的
    return this.canvas.toDataURL(type, quality); //将canvas转成base64第二个参数是设置质量（仅在图片类型为jpeg时生效越大越好最大1）
  }
  drawImg(
    img: CanvasImageSource,
    x: number,
    y: number,
    w: number,
    h: number,
    isCover = true
  ) {
    // 画图片
    let history = this.history;
    if (isCover) {
      //被历史画笔覆盖状态
      for (let i = 0; i < history.length; i++) {
        //图片覆盖画笔的状态总在历史画笔最前面
        if (history[i].isCover === undefined || history[i].isCover === false) {
          //图片覆盖画笔的状态需要插入到历史画笔中图片覆盖画笔状态的最后一张
          history.splice(i, 0, { img, x, y, w, h, isCover });
          break; //不要去掉
        }
        if (!history[i + 1]) history.push({ img, x, y, w, h, isCover }); //遍历过程如果全是图片覆盖画笔的状态的图片放到最后一个(必须放break后面)
      }
      if (history.length === 0) history.push({ img, x, y, w, h, isCover }); //没有历史画笔直接放入
    } else {
      //覆盖历史画笔状态
      history.push({ img, x, y, w, h, isCover });
    }
    this.drawStep = this.drawStep + 1;
    this.historyDrawPen(this.history);
  }
  downland(
    name: string,
    quality: number = 1,
    type: "image/png" | "image/jpeg" = "image/png"
  ) {
    // 下载canvas图片文件
    let dom = document.createElement("a");
    dom.download = name;
    dom.href = this.getbase64(quality, type);
    document.body.appendChild(dom);
    dom.click();
    document.body.removeChild(dom);
  }
  getBase64ImgFile(
    quality: number = 1,
    type: "image/png" | "image/jpeg" = "image/png",
    name: string
  ) {
    // 获取canvas的file类型
    let str = this.getbase64(quality, type).split(",");
    let mime = (str[0] || "").match(/:(.*?);/);
    let bstr = atob(str[1]); //解码使用 base-64 编码的字符串
    let n = bstr.length;
    let u8arr = new Uint8Array(n); //该Uint8Array类型数组表示的8位无符号整数数组。内容被初始化为0。建立后，您可以使用对象的方法或标准数组索引语法（即，使用括号表示法）引用数组中的元素。
    for (let i = 0; i < n; i++) {
      u8arr[i] = bstr.charCodeAt(i); //charCodeAt() 方法可返回指定位置的字符的 Unicode 编码
    }
    return (
      mime &&
      mime[0] &&
      new File([u8arr], name, {
        type: mime[0],
      })
    );
  }
}
